<?php

declare(strict_types=1);

namespace App\Api\Middleware;

use App\Constants\ResponseCode;
use DateTimeImmutable;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Response;
use Hyperf\Logger\LoggerFactory;
use Lcobucci\JWT\Token\RegisteredClaims;
use Mine\MineRequest;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Throwable;

use function Hyperf\Config\config;

class ResponseMiddleware implements MiddlewareInterface
{
    private string $scene;

    public function __construct(protected ContainerInterface $container)
    {
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        // 请求场景
        $this->scene = request_scene();

        // 请求类型设置
        $request = $request->withHeader('Accept', 'application/json');

        // 记录访问日志
        $this->accessLog();

        // 响应
        $response = $handler->handle($request);

        // 过滤
        if ($this->filter($response)) {
            return $response;
        }

        // 处理公共返回
        $result = [
            'code' => ResponseCode::SUCCESS_DEFAULT,
            'msg' => ResponseCode::getMessage(ResponseCode::SUCCESS_DEFAULT),
            'data' => (object)json_decode($response->getBody()->getContents()),
        ];

        // 刷新Token
        $response = $this->refreshToken($response);

        return (new Response($response))->json($result);
    }

    /**
     * 记录访问日志
     * @return void
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     */
    private function accessLog(): void
    {
        $request = $this->container->get(RequestInterface::class);
        $accessLog = [
            'uid' => api_member_id($this->scene),
            'method' => $request->getMethod(),
            'path' => $request->path(),
            'params' => json_encode($request->all(), JSON_UNESCAPED_UNICODE),
            'user_agent' => $request->header('User-Agent', ''),
            'version' => $request->header('Version', ''),
            'ip' => $this->container->get(MineRequest::class)->ip(),
        ];
        $this->container->get(LoggerFactory::class)->get('Access')->info('', $accessLog);
    }

    /**
     * 过滤
     * @param ResponseInterface $response
     * @return bool
     */
    private function filter(ResponseInterface $response): bool
    {
        $request = $this->container->get(RequestInterface::class);

        if ($request->is(config('filter_response_urls'))) {
            return true;
        }

        if ($response instanceof BinaryFileResponse) {
            return true;
        }

        return false;
    }

    /**
     * 刷新Token
     * @param ResponseInterface $response
     * @return ResponseInterface
     */
    private function refreshToken(ResponseInterface $response): ResponseInterface
    {
        try {
            $user = user($this->scene);
            /** @var DateTimeImmutable $exp */
            $exp = $user->getJwt()->getParserData()[RegisteredClaims::EXPIRATION_TIME];
            $diff = $exp->getTimestamp() - time();
            if ($diff < 0 || $diff > 300) {
                return $response;
            }

            return $response->withHeader('set-token', $user->refresh());
        } catch (Throwable $e) {
            return $response;
        }
    }
}
